/**
 * ...
 * @author 
 */

package scuts.ds;

private typedef BaseHash<D,T> = {
    public function iterator() : Iterator<T>;
    public function keys() : Iterator<D>;
    public function remove( key : D ) : Bool;
    public function exists( key : D ) : Bool;
    public function get( key : D ) : Null<T>;
    public function set( key : D, value : T ) : Void;
}

class DefaultSet<A,T> implements Set<T>
{
    
	var db:BaseHash<A, Array<T>>;
	
	var equals: T->T->Bool;
	var hasEquals:Bool;
	var hash: T->A;
	
	public function new(db:BaseHash<A, Array<T>>, hash:T->A, ?equals:T->T->Bool) 
	{
		this.db = db;
		this.hash = hash;
		this.equals = equals;
		this.hasEquals = equals != null;
	}
    
	inline function elemsEquals (a:T, b:T):Bool {
		return hasEquals ? equals(a, b) : (a == b);
	}
	
	public function addAll (iterator:Iterator<T>):Void {
		for (e in iterator) {
			add(e);
		}
		
	}
	
	public function add (elem:T):Bool {
		
		var h:A = hash(elem);
		
		var arr;
		if (!db.exists(h)) {
			arr = [];
			db.set(h, arr);
		} else {
			arr = db.get(h);
		}
		
		for (i in arr) {
			if (elemsEquals (i, elem)) return false;
		}
		
		arr.push( elem );
		return true;
	}
	
	public function remove (elem:T):Null<T>
	{
		
		var h:A = hash(elem);
		
		var arr = db.get(h);
		
		if (arr == null) return null;
		
		
		for (i in arr) {
			if ( elemsEquals( i, elem ) ) {
				arr.remove(i);
				if (arr.length == 0) {
					db.remove(h);
				}
				return elem;
			}
		}
		
		return null;	
	}
	
	public function exists (elem:T):Bool 
	{
		
		var h:A = hash(elem);
		
		var arr = db.get(h);
		
		if (arr == null) return false;
		
		for (i in arr) 
		{
			if ( elemsEquals (i, elem) ) return true;
		}
		
		return false;
	}
	
	public function elems ():Iterable<T>
	{
		var all= [];
		
		for (i in db) {
			for (j in i) {
				all.push(j);
			}
		}
		return all;
	}
	
	public function iterator ():Iterator<T>
	{
		return elems().iterator();
	}
	
	public function removeAll ():Void
	{
		for (i in db) {
			while (i.length > 0) {
				i.pop();
			}
		}
		for (i in db.keys()) {
			db.remove(i);
		}
		
		
	}
	
}



